第5章 基本引用类型(1)

虽然从技术上讲 JavaScript 是一门面向对象语言,但是缺少传统的面向对象编程语言所具备的某些基本结构,包括类和接口。因此引用类型虽然有点像类,但跟类并不是一个概念,仅仅是一个把数据和功能组织到一起的结构。

对象被认为是某个特定引用类型的实例,新对象通过使用 new 操作符后跟一个构造函数来创建

let now = new Date();

这行代码创建了引用类型 Date 的一个新实例,并将它保存在变量 now 中。Date()在这里就是构造函数,它负责创建一个只有默认属性和方法的简单对象.

Date

要创建日期对象,就使用 new 操作符来调用 Date 构造函数:

let now = new Date();

在不给 Date 构造函数传参数的情况下,创建的对象将保存当前日期和时间。要基于其他日期和时间创建日期对象,必须传入其毫秒表示。

Date还有两个辅助方法:Date.parse() 和 Date.UTC() 来获取毫秒数

Date.parse()方法接收一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数。目前支持以下日期格式:

let someDate = new Date(Date.parse("May 23, 2019"));

// Date构造函数会隐式调用 Date.parse 方法
let someDate = new Date("May 23, 2019");

Date.UTC()方法通过参数的形式接收目标日期。传给 Date.UTC() 的参数是年、零起点月数(1 月是 0,2 月是 1,以此类推)、日(1~31)、时(0~23)、 分、秒和毫秒。这些参数中,只有年和月是必需的。如果不提供日,那么默认为 1 日,其他参数的默认值都是 0。

// GMT时间2000年1月1日零点
let y2k = new Date(Date.UTC(2000, 0));
// GMT时间2005年5月5日下午5点55分55秒
let allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));

// Date构造函数会隐式调用 Date.UTC 方法
// 但是这样创建的是本地日期,不是 GMT 日期
// 本地时间2000年1月1日零点
let y2k = new Date(2000, 0);
// 本地时间2005年5月5日下午5点55分55秒
let allFives = new Date(2005, 4, 5, 17, 55, 55);

还可以通过 Date.now() 方法获取代码执行时的毫秒数,一般用于计算时间差或网络请求校验

// 起始时间
let start = Date.now();
// 调用函数 
doSomething();
// 结束时间
let stop = Date.now(), result = stop - start;
// 计算函数运行所消耗的时间

Date类型提供了一些默认的格式化日期的方法:

这些方法会因浏览器而异,因此不能用于在用户界面上一致地显示日期,如果需要单独提取日期中的特定部分,则需要使用其他API(详见原文列表)

RegExp

JS通过 RegExp 类型进行正则匹配,每个正则表达式可以带0~n个 flages ,用于控制正则表达式的行为

let expression = /pattern/flags;

使用不同模式和标记可以创建出各种正则表达式,例如:

// 匹配字符串中的所有"at"
let pattern1 = /at/g;
// 匹配第一个"bat"或"cat",忽略大小写 
let pattern2 = /[bc]at/i;
// 匹配所有以"at"结尾的三字符组合,忽略大小写
let pattern3 = /.at/gi;

所有元字符在模式中也必须转义,包括:( [ { \ ^ $ | ) ] } ? * + .

元字符在正则表达式中都有一种或多种特殊功能,所以要匹配上面这些字符本身,就必须使用反斜杠来转义

// 匹配第一个"bat"或"cat",忽略大小写 
let pattern1 = /[bc]at/i;
// 匹配第一个"[bc]at",忽略大小写 
let pattern2 = /\[bc\]at/i;
// 匹配所有以"at"结尾的三字符组合,忽略大小写 
let pattern3 = /.at/gi;
// 匹配所有".at",忽略大小写
let pattern4 = /\.at/gi;

正则表达式也可以使用 RegExp 构造函数来创建,它接收两个参数:模式字符串和(可选的)标记字符串,以下两个表达式是等效的:

// 匹配第一个"bat"或"cat",忽略大小写 
let pattern1 = /[bc]at/i;
// 跟 pattern1 一样,只不过是用构造函数创建的 
let pattern2 = new RegExp("[bc]at", "i");

此外,使用 RegExp 也可以基于已有的正则表达式实例,并可选择性地修改它们的标记:

const re1 = /cat/g;
console.log(re1);  // "/cat/g"
// 沿用之前的flag
const re2 = new RegExp(re1);
console.log(re2);  // "/cat/g"
// 修改了原来的flag,从 g 改为了 i
const re3 = new RegExp(re1, "i");
console.log(re3);  // "/cat/i"

每个 RegExp 实例都有下列属性,提供有关模式的各方面信息:

let pattern = /\[bc\]at/i;
console.log(pattern.global); // false
console.log(pattern.ignoreCase); // true
console.log(pattern.multiline); // false
console.log(pattern.lastIndex); // 0
console.log(pattern.source); // "\[bc\]at"
console.log(pattern.flags); // "i"
// 使用RegExp构造函数具有同样效果,可以自行实现

RegExp 实例的主要方法是 exec(),主要用于配合捕获组使用。这个方法只接收一个参数,即要应用模式的字符串。如果找到了匹配项,则返回包含第一个匹配信息的数组;如果没找到匹配项,则返回 null。返回的数组虽然是 Array 的实例,但包含两个额外的属性:index 和 input。index 是字符串中匹配模式的起始位置,input 是要查找的字符串。这个数组的第一个元素是匹配整个模式的字符串,其他元素是与表达式中的捕获组匹配的字符串。如果模式中没有捕获组,则数组只包含一个元素。

let text = "mom and dad and baby";
let pattern = /mom( and dad( and baby)?)?/gi;

let matches = pattern.exec(text);
console.log(matches.index); //0
console.log(matches.input); // "mom and dad and baby"
console.log(matches[0]); // "mom and dad and baby"
console.log(matches[1]); // " and dad and baby"
console.log(matches[2]); // " and baby"

在上面例子中,模式包含两个捕获组:最内部的匹配项" and baby",以及外部的匹配项" and dad"或" and dad and baby"。调用 exec()后找到了一个匹配项。因为整个字符串匹配模式,所以 matchs 数组的 index 属性就是 0。数组的第一个元素是匹配的整个字符串,第二个元素是匹配第一个捕获组的字符串,第三个元素是匹配第二个捕获组的字符串。

如果模式设置了全局标记,则每次调用 exec()方法会返回下一个匹配的信息。如果没有设置全局标记,则无论对同一个字符串调用多少次 exec(),也只会返回第一个匹配的信息

let text = "cat, bat, sat, fat";
let pattern = /.at/; // 没有设置全局标记
// 调用 exec()只返回第一个匹配项("cat")
let matches = pattern.exec(text); 
console.log(matches.index); // 0 
console.log(matches[0]); // cat 
console.log(pattern.lastIndex); // 0
// 第二次进行匹配
matches = pattern.exec(text);
console.log(matches.index); // 0 
console.log(matches[0]); // cat 
console.log(pattern.lastIndex); // 0,lastIndex 在非全局模式下始终不变
let text = "cat, bat, sat, fat";
let pattern = /.at/g; // 设置了全局标记
// 每次调用 exec()都会返回字符串中的下一个匹配项
let matches = pattern.exec(text);
console.log(matches.index); // 0
console.log(matches[0]); // cat
console.log(pattern.lastIndex);  // 3
// 在全局匹配模式下,每次调用exec()都会更新 lastIndex 值
// 以反映上次匹配的最后一个字符的索引
matches = pattern.exec(text);
console.log(matches.index); // 5 
console.log(matches[0]); // bat 
console.log(pattern.lastIndex); // 8

matches = pattern.exec(text);
console.log(matches.index);      // 10
console.log(matches[0]);         // sat
console.log(pattern.lastIndex);  // 13

如果模式设置了粘附标记 y,则每次调用 exec() 就只会在 lastIndex 的位置上寻找匹配项,并且粘附标记会覆盖全局标记。

let text = "cat, bat, sat, fat";
let pattern = /.at/y;

let matches = pattern.exec(text);
console.log(matches.index); // 0
console.log(matches[0]); // cat
console.log(pattern.lastIndex); // 3

// 以索引3对应的字符开头找不到匹配项,因此exec()返回null
// exec()没找到匹配项,于是将lastIndex设置为0
matches = pattern.exec(text); 
console.log(matches); // null
console.log(pattern.lastIndex); // 0

// 向前设置lastIndex可以让粘附的模式通过exec()找到下一个匹配项: 
pattern.lastIndex = 5;
matches = pattern.exec(text);
console.log(matches.index); // 5
console.log(matches[0]); // bat
console.log(pattern.lastIndex); // 8

正则表达式的另一个方法是 test(),接收一个字符串参数。如果输入的文本与模式匹配,则返回 true,否则返回 false,常用于条件判断:

let text = "000-00-0000";
let pattern = /\d{3}-\d{2}-\d{4}/;
if (pattern.test(text)) {
  console.log("The pattern was matched.");
}

RegExp 构造函数本身也有几个属性,这些属性适用于作用域中的所有正则表达式,而且会根据最后执行的正则表达式操作而变化,可以通过全名或者简写进行访问

全名 简写 说明
input $_ 最后搜索的字符串
lastMatch $& 最后匹配的文本
lastParen $+ 最后匹配的捕获组
leftContext $` input字符串中出现在lastMatch前面的文本
rightContext $' input字符串中出现在lastMatch后面的文本
let text = "this has been a short summer";
let pattern = /(.)hort/g;
if (pattern.test(text)) {
  console.log(RegExp.input); // this has been a short summer
  console.log(RegExp.leftContext); // this has been a
  console.log(RegExp.rightContext); // summer
  console.log(RegExp.lastMatch); // short
  console.log(RegExp.lastParen); // s
}

以上属性名也可以替换成简写形式

let text = "this has been a short summer";
let pattern = /(.)hort/g;
if (pattern.test(text)) {
  console.log(RegExp.$_); // this has been a short summer
  console.log(RegExp["$`"]); // this has been a
  console.log(RegExp["$'"]); // summer
  console.log(RegExp["$&"]); // short
  console.log(RegExp["$+"]); // s
}

RegExp 还有其他几个构造函数属性,可以存储最多 9 个捕获组的匹配项,这些属性通过 RegExp.$1~RegExp.$9 来访问

let text = "this has been a short summer";
let pattern = /(..)or(.)/g;

if (pattern.test(text)) {
  console.log(RegExp.$1);  // sh
  console.log(RegExp.$2);  // t
}